package mweb

import (
	"encoding/base32"
	"encoding/json"
	"github.com/go-redis/redis"
	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"
	"net/http"
	"strings"
	"time"
)

type RedisStore struct {
	Options     *sessions.Options
	RedisClient *redis.Client
	Codecs      []securecookie.Codec
	prefix      string
}

func NewRedisStore(redisClient *redis.Client, prefix string, keyPairs ...[]byte) *RedisStore {
	return &RedisStore{
		Codecs: securecookie.CodecsFromPairs(keyPairs...),
		Options: &sessions.Options{
			Path:   "/",
			MaxAge: 86400 * 30,
		},
		RedisClient: redisClient,
		prefix:      prefix,
	}
}

func (domain *RedisStore) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(domain, name)

}

func (domain *RedisStore) New(r *http.Request, name string) (*sessions.Session, error) {
	session := sessions.NewSession(domain, name)
	opts := *domain.Options
	session.Options = &opts
	session.IsNew = true
	var err error
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, domain.Codecs...)
		if err == nil {
			value := map[string]any{}
			if session.Values == nil {
				session.Values = map[interface{}]interface{}{}
			}
			err = domain.loadValue(session.ID, &value)
			for k, v := range value {
				session.Values[k] = v
			}
			if err == nil {
				session.IsNew = false
			}
		}
	}
	return session, err
}

func (domain *RedisStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	if session.Options.MaxAge <= 0 {
		if err := domain.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
		return nil
	}
	if session.ID == "" {
		// Because the ID is used in the filename, encode it to
		// use alphanumeric characters only.
		session.ID = strings.TrimRight(
			base32.StdEncoding.EncodeToString(
				securecookie.GenerateRandomKey(32)), "=")
	}
	if err := domain.save(session); err != nil {
		return err
	}
	encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
		domain.Codecs...)
	if err != nil {
		return err
	}
	http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	return nil
}
func (domain *RedisStore) loadValue(sessionId string, dest any) error {
	cmd := domain.RedisClient.Get(domain.getKey(sessionId))
	if b, err := cmd.Bytes(); err == nil {
		err = json.Unmarshal(b, dest)
		if err != nil {
			return err
		}
		return nil
	} else {
		return err
	}
}
func (domain *RedisStore) delete(session *sessions.Session) error {
	cmd := domain.RedisClient.Del(domain.getKey(session.ID))
	_, err := cmd.Result()
	return err
}
func (domain *RedisStore) save(session *sessions.Session) error {
	newMap := make(map[string]interface{})
	for k, v := range session.Values {
		if ks, b := k.(string); b {
			newMap[ks] = v
		} else {
			panic("unsupported not string key")
		}
	}

	if b, err := json.Marshal(&newMap); err == nil {
		s := string(b)
		return domain.RedisClient.Set(domain.getKey(session.ID), s, time.Duration(session.Options.MaxAge)*time.Second).Err()

	} else {
		return err
	}
}
func (domain *RedisStore) getKey(sessionId string) string {
	return domain.prefix + ":sessions:" + sessionId
}
